﻿#region using
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Reflection;
using System.Text;

using RRRSRoguelike.Entities;
using RRRSRoguelike.Entities.Creatures;
using RRRSRoguelike.Entities.Props;
using RRRSRoguelike.Enums;
using RRRSRoguelike.Helpers;
using RRRSRoguelike.Helpers.Actions;
using RRRSRoguelike.Helpers.Visualizer;
using RRRSRoguelike.Managers;
using RRRSRoguelike.RuleSets;
using RRRSRoguelikeData;

#endregion

namespace RRRSRoguelike.Screens
{
	/// <summary>
	/// Provides the UI and controls flow of in-game screens.
	/// </summary>
	public class GameScreen : Screen
	{
		#region variables
		
		//FIXME: Not sure these belongs here
		//provides a convenient pointer to the game player
		private Player player;
		//holds a reference to the game logic class
		private RuleSet ruleSet;
		private bool processMonsters;
		
		//used to exit game loop
		private bool ExitGame;

		/// <summary>
		/// Event called on user exit
		/// </summary>
		public event EventHandler Exit = delegate{};
		/// <summary>
		/// Event called when game finishes.
		/// </summary>
		public event EventHandler EndGame = delegate{};
		
		#endregion

		#region constructors

		/// <summary>
		/// constructor
		/// </summary>
		public GameScreen()
		{
		}

		#endregion

		#region methods

		//used to display the dungeon screen
		/// <summary>
		/// display the screen
		/// </summary>
		public override void Show()
		{
			active = true;
			ExitGame = false;
			Clear();
			drawPanels = true;
			base.Show();
			StartGame();
		}
		
		/// <summary>
		/// Start the game
		/// </summary>
		protected virtual void StartGame()
		{

			Setup();

			//-----------------------------------------------------------------
			MainGameLoop();
			//-----------------------------------------------------------------
			
			
			if(!ExitGame)
			{
				EndGame(this,EventArgs.Empty);
				SaveTempHighScore();
				
				ShowVictoryScreen();
			}
			
			Exit(this,EventArgs.Empty);
		}

		void SaveTempHighScore()
		{
			//HACK: this probably belongs in a ruleset
			//Saves a temporary score in session data for the highscore screen to process.
			ApplicationManager.Instance.Profile.Session.tempScore.Data.Clear();
			
			ApplicationManager.Instance.Profile.Session.tempScore.Data.Add("ID",HighScoreHelper.GetUniqueID(ApplicationManager.Instance.Profile.HighScores));
			
			//HACK: we should create a dictionary and enum to standardize naming of entries.
			ApplicationManager.Instance.Profile.Session.tempScore.Data.Add("Name", ApplicationManager.Instance.Profile.Name);

			ApplicationManager.Instance.Profile.Session.tempScore.Data.Add("Score", player.Stats.StatList["Score"].ToString());

			//HACK: we need a variable to retrieve the current ruleset
			ApplicationManager.Instance.Profile.Session.tempScore.Data.Add("Ruleset", "Vanilla");

			//HACK: this should really be the version of the ruleset
			// this would mean the rulesets ought to be in a separate solution
			ApplicationManager.Instance.Profile.Session.tempScore.Data.Add("Version", "v" + Assembly.GetEntryAssembly().GetName().Version);

			ApplicationManager.Instance.Profile.Session.tempScore.Data.Add("Seed", ApplicationManager.Instance.Profile.Session.TempSeed.ToString());
		}
		
		void Setup()
		{
			//Needs to be done before initializing the ruleset
			GameManager.Instance.GameScreen = this;
			
			//FIXME: set ruleset from options menu
			switch(Properties.Settings.Default.RuleSet)
			{
				case "Vanilla":
					ruleSet = new VanillaRuleSet();
				break;
				
				default:
					ruleSet = new VanillaRuleSet();
				break;			
			}
			
			ruleSet.Initialize();
			
			player = GameManager.Instance.CreatureManager.Player;
			
			processMonsters = GameManager.Instance.CreatureManager.processMonsters;
			
			SetupPanels();
			SetDefaultKeys();
			//Screen within a screen - later each screen may need its own screen stack
			ShowWelcomeScreen();
			
			InitialDraw();
		}

		private long lastCall;
		//HACK: remove hardcoded value
		private long delay = 16;
		private void MainGameLoop()
		{
			//Limit calls per second
			if (ApplicationManager.Instance.Time.ElapsedMilliseconds > lastCall + delay)
			{
				lastCall = ApplicationManager.Instance.Time.ElapsedMilliseconds;

				while (GameManager.Instance.IsGameActive)
				{
					//read input
					GetInput();
		
					ruleSet.ProcessInput();
					
					//check exit
					if (ExitGame)
						return;

					//do logic
					ruleSet.DoLogic();

					//Draw
					DoDraw();
				}
			}
		}
		
		///Gets key pressed from user
		public void GetInput()
		{
			ApplicationManager.Instance.InputManager.ReadKeys();
		}

		private void DoDraw()
		{
			if (drawPanels)
			{
				foreach (var panel in PanelList)
					panel.Draw();

				drawPanels = false;
			}
			DisplayHUD();

			GameManager.Instance.DrawDungeon();
		}

		#region Screens

		void ShowVictoryScreen()
		{
			//The screen stack is LIFO so these appear backwards

			ApplicationManager.Instance.ScreenLoader.Clear();
			
			ApplicationManager.Instance.ScreenLoader.Add(
				GameManager.Instance.PlayerHasWon.Value
				? new TextScreen(GameManager.Instance.Texts["Victory"])
				: new TextScreen(GameManager.Instance.Texts["GameOver"]));
			
			ApplicationManager.Instance.ScreenLoader.Add(new HighScoreScreen());
			ApplicationManager.Instance.ScreenLoader.Add(new PauseScreen());
			
			ApplicationManager.Instance.LoadFromScreenLoader();
			
		}

		void ShowWelcomeScreen()
		{
			var textScreen = new TextScreen(GameManager.Instance.Texts["Welcome"]);
			textScreen.Show();
		}
		
		/// <summary>
		/// Show help screen
		/// </summary>
		public void ShowHelpScreen()
		{
			var hs = new TextScreen(GameManager.Instance.Texts["Help"]);
			hs.Show();
			GameManager.Instance.DungeonVisualizer.ForceFullVisualization(GameManager.Instance.DungeonLevel);
		}

		/// <summary>
		/// show quit screen
		/// </summary>
		public void ShowQuitScreen()
		{
			var screen = new PauseScreen();
			if (screen.ShowYesNo())
			{
				ExitGame = true;
			}
			else
			{
				GameManager.Instance.DungeonVisualizer.ForceFullVisualization(GameManager.Instance.DungeonLevel);
			}
		}

		#endregion

		#region Menu

		/// <summary>
		/// Display build menu
		/// </summary>
		public void DoBuildMenu()
		{
			//controls menu input loop
			ShowBuildMenu = true;

			//could inject a menu panel here
			var mp = (MenuPanel)PanelList[(int)Panels.Left];
			var menu = mp.Menu;

			menu.Clear();
			SetupMenu(menu);
			mp.DrawMenu();

			while (ShowBuildMenu) {
				mp.HandleInput();
			}
			PanelList[(int)Panels.Left].Clear();
		}

		bool ShowBuildMenu;
		void SetupMenu(Menu menu)
		{
			//TODO: this should be more data driven And belongs in a ruleset
			//Need to check available actions and populate the menu.
			menu.Add(new MenuItem("Wall", SelectWall));
			menu.Add(new MenuItem("Door", SelectDoor));
			menu.Add(new MenuItem("Remove", SelectRemove));
			menu.Add(new MenuItem("Cancel", SelectCancel));
		}

		//Event method
		void SelectWall(object sender, EventArgs e)
		{
			ActionHelper.Build(new Tile(player.Position,
			                            GameManager.Instance.Dungeon.Data.Tiles["Wall"]));

			ShowBuildMenu = false;
		}

		//Event method
		void SelectDoor(object sender, EventArgs e)
		{
			ActionHelper.Build(new Door(new Point(0, 0)));
			ShowBuildMenu = false;
		}

		void SelectRemove(object sender, EventArgs e)
		{
			ActionHelper.Remove();
			ShowBuildMenu = false;
		}

		//Event method
		void SelectCancel(object sender, EventArgs e)
		{
			ShowBuildMenu = false;
			//TODO: belongs in game logic
			processMonsters = player.Flags["hasMoved"] = false;
		}

		#endregion

		#region Draw
		private void InitialDraw()
		{
			//Swapping these two lines around stopped the flicker, no idea why!
			//this draws the panels
			GameManager.Instance.DungeonVisualizer.ForceFullVisualization(GameManager.Instance.DungeonLevel);
			//this does what it says on the tin.
			GameManager.Instance.ForceRedraw();
			
			DrawTitle();
		}

		/// <summary>
		/// Draw title
		/// </summary>
		public void DrawTitle()
		{
			PanelList[0].Clear();
			PanelList[0].Write(GameManager.Instance.DungeonLevel.ToString(), 0, ConsoleColor.White, TextAlignment.Center);
		}

		/// <summary>
		/// redraw
		/// </summary>
		public void ReDraw()
		{
			drawPanels = true;
			DrawTitle();
			DoDraw();
		}

		//FIXME: This belongs in a ruleset
		private void DisplayHUD()
		{
			//HACK: we should have a helper for drawing tables.
			int titlecol = 20;
			int valuecol = 10;
			
			if (GameManager.Instance.CreatureManager.Player.WeaponSlot != null)
			{
				clear = false;
				var weapon = GameManager.Instance.CreatureManager.Player.WeaponSlot;

				if (weapon.UsesRemaining != olduses)
				{
					var sb = new StringBuilder((weapon.Name + ":"));
					string useStars  = "";
					for (var i = 0; i < weapon.UsesRemaining; i++)
					{
						useStars += "*";
					}
					//useStars = useStars;
					//sb.Append(useStars);
					string str = Visualizer.PrepareRow(new string[]{sb.ToString(),useStars}, new int[]{titlecol,valuecol});
					PanelList[(int)Panels.Right].Clear();
					PanelList[(int)Panels.Right].Write(str, 0, ConsoleColor.White, TextAlignment.Center);
				}
				olduses = weapon.UsesRemaining;
			}
			else if (!clear)
			{
				PanelList[(int)Panels.Right].ClearLine(0);
				clear = true;
			}

			//FIXME: this is ruleset specific and the padding should be automatic and only implemented once
			PanelList[(int)Panels.Right].Write((Properties.Strings.StatsMonstersKilled + ":").PadRight(titlecol) +
			                                   player.Stats.StatList["MonstersKilled"].ToString().PadRight(valuecol), 1, ConsoleColor.White, TextAlignment.Center);

			PanelList[(int)Panels.Right].Write((Properties.Strings.StatsStepsTaken + ": ").PadRight(titlecol) +
			                                   player.Stats.StatList["StepsTaken"].ToString().PadRight(valuecol),2, ConsoleColor.White, TextAlignment.Center);
			
			PanelList[(int)Panels.Right].Write((Properties.Strings.StatsWeaponsGrabbed + ": ").PadRight(titlecol) +
			                                   player.Stats.StatList["WeaponsGrabbed"].ToString().PadRight(valuecol), 3, ConsoleColor.White, TextAlignment.Center);
            PanelList[(int)Panels.Right].Write((Properties.Strings.HUDScore + ": ").PadRight(titlecol) +
                                                player.Stats.StatList["Score"].ToString().PadRight(valuecol), 6, ConsoleColor.White, TextAlignment.Center);
		}
		#endregion
		
		/// <summary>
		/// show message
		/// </summary>
		/// <param name="message"></param>
		/// <param name="wait"></param>
		public void ShowMessage(string message,bool wait = true)
		{
			PanelList[(int)Panels.Bottom].Write(message, 0, ConsoleColor.White, TextAlignment.Center);
			if(wait)
			{
				WaitKey();
				PanelList[(int)Panels.Bottom].Clear();
			}
		}

		/// <summary>
		/// Get direction from user
		/// </summary>
		/// <returns>Returns a direction.  direction.Wait can represent cancel</returns>
		public Direction GetDirectionFromUser()
		{
			bool polling = true;
			var ActionManager = GameManager.Instance.ActionManager;
			//TODO:we need a prompt user method here
			PanelList[(int)Panels.Bottom].Write(Properties.Strings.ChooseDirection, 0, ConsoleColor.White, TextAlignment.Center);

			Direction direction = Direction.Wait;
			while (polling)
			{
				GetInput();
				direction = GetDirection();;
				if (direction != Direction.Wait)
				{
					polling = false;
				}
				//HACK: using wait to represent cancel
				else if (ActionManager.IsActionPushed(Actions.Quit) ||
				         ActionManager.IsActionPushed(Actions.Wait))
					return Direction.Wait;
			}

			PanelList[(int)Panels.Bottom].Clear();

			return direction;
		}

		//FIXME: needs a more accurate name.
		private static Direction GetDirection()
		{
			var ActionManager = GameManager.Instance.ActionManager;
			Direction direction;

			if (ActionManager.IsActionPushed(Actions.North))
				direction = Direction.N;
			else if (ActionManager.IsActionPushed(Actions.South))
				direction = Direction.S;
			else if (ActionManager.IsActionPushed(Actions.East))
				direction = Direction.E;
			else if (ActionManager.IsActionPushed(Actions.West))
				direction = Direction.W;
			else
				direction = Direction.Wait;

			return direction;
		}
		//used to control screen update
		int olduses;
		private void resetOldUses(object sender, EventArgs e)
		{
			olduses = -1;
		}
		bool clear;
		
		private void SetupPanels()
		{
			PanelList = new List<Panel>
			{
				//Panels.Top;
				new Panel(new Point(1, 1), Visualizer.GetScreenWidth() - 2, 5),
				//Panels.Bottom;
				new Panel(new Point(1, 27), Visualizer.GetScreenWidth() - 2, 20),
				//Panels.Left;
				new MenuPanel(new Point(1, 5), 38, Constants.DungeonHeight + 2),
				//Panels.Right;
				new Panel(new Point(60, 5), 38, Constants.DungeonHeight + 2)
			};
			
			//Hook up events. resets old uses forcing HUD redraw on weapon change.
			GameManager.Instance.CreatureManager.Player.WeaponChanged += resetOldUses;
		}

		//This is more like data
		/// <summary>
		/// set default keys
		/// </summary>
		public void SetDefaultKeys()
		{
			var actionManager = GameManager.Instance.ActionManager;

			actionManager.SetButtons(Actions.North, ConsoleKey.UpArrow);
			actionManager.SetButtons(Actions.South, ConsoleKey.DownArrow);
			actionManager.SetButtons(Actions.East, ConsoleKey.RightArrow);
			actionManager.SetButtons(Actions.West, ConsoleKey.LeftArrow);
			actionManager.SetButtons(Actions.North, ConsoleKey.NumPad8);
			actionManager.SetButtons(Actions.South, ConsoleKey.NumPad5);
			actionManager.SetButtons(Actions.East, ConsoleKey.NumPad6);
			actionManager.SetButtons(Actions.West, ConsoleKey.NumPad4);
			actionManager.SetButtons(Actions.Jump, ConsoleKey.J);
			actionManager.SetButtons(Actions.Shoot, ConsoleKey.F);
			actionManager.SetButtons(Actions.Shoot, ConsoleKey.Z);
			actionManager.SetButtons(Actions.Pick, ConsoleKey.G);
			actionManager.SetButtons(Actions.Drop, ConsoleKey.P);
			actionManager.SetButtons(Actions.Drop, ConsoleKey.D);
			actionManager.SetButtons(Actions.Build, ConsoleKey.B);
			actionManager.SetButtons(Actions.Trap, ConsoleKey.T);
			actionManager.SetButtons(Actions.Open, ConsoleKey.O);
			actionManager.SetButtons(Actions.Look, ConsoleKey.L);
			actionManager.SetButtons(Actions.Wait, ConsoleKey.OemPeriod);
			actionManager.SetButtons(Actions.Quit, ConsoleKey.Q);
			actionManager.SetButtons(Actions.Quit, ConsoleKey.Escape);
			actionManager.SetButtons(Actions.Help, ConsoleKey.F1);
			actionManager.SetButtons(Actions.FollowToggle, ConsoleKey.F2);
			actionManager.SetButtons(Actions.SoundToggle, ConsoleKey.F3);
			actionManager.SetButtons(Actions.ChangeAnimationSpeed, ConsoleKey.F4);
			actionManager.SetButtons(Actions.ToggleGodMode, ConsoleKey.F9);
		}
		#endregion
		
		/// <summary>
		/// clear message panel
		/// </summary>
		public void ClearMessages()
		{
			PanelList[(int)Panels.Bottom].Clear();
		}
	}
}
